#include <jni.h>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <android/log.h>

#ifndef LOGI(x...)
#endif
#define LOGI(x...) __android_log_print(ANDROID_LOG_INFO,"JNILearn",x)

/**
 * 记住，类名的路径不要以 '/'开头，写成
 * "/example/bill/com/jnilearn/MainActivity" 将会找不到类
 * */
#define CLAZZ_MAINACTIVITY "example/bill/com/jnilearn/MainActivity"
#define CLAZZ_EVENTINFO "example/bill/com/jnilearn/EventInfo"
#define CLAZZ_EVENTTYPE "example/bill/com/jnilearn/EventInfo$EventType"
#define CLAZZ_JNIPROCESSOR "example/bill/com/jnilearn/JNIProcessor"

/*
 * 全局的jvm变量，可以在进程内共享
 * */
_JavaVM *gJvm;

/*
 * 工作线程work_thread
 * */
pthread_t tid_work_thread;

/**
 * 映射java当中的类和C++当中的类
 * */
static jclass gclazzMainActivity = NULL;
static jclass gclazzEventInfo = NULL;
static jclass gclazzEventType = NULL;
static jclass gclazzJNIProcessor = NULL;
static jmethodID gNewEventMethod = NULL;
static jmethodID gProcessNativeMethod = NULL;

typedef enum tag_EventType{
    en_event_ok         = 0,
    en_event_failed     = 1
}EventType;

typedef struct tag_EventInfo{
    EventType eventType;
    int msgNo;
    std::string msgData;
} EventInfo;

/**
 * java层调用 System.loadLibrary("native-lib") 时会调用到该函数
 * 一般在该函数内做回调函数注册，类映射等动作
 * */
extern "C"
JNIEXPORT jint JNICALL  JNI_OnLoad(JavaVM *ajvm, void *reserved)
{
    LOGI(" library load on call jni ");
    jclass tmpClazz;
    jmethodID tmpMethodID;
    jint result = -1;
    JNIEnv *currentEnv = NULL;

    /**
     * Jvm指针是进程内共享的，可以保存jvm指针到全局变量
     * */
    gJvm = ajvm;
    /**
     * 获取当前线程的JNIEnv
     * */
    if (gJvm->GetEnv((void **) &currentEnv, JNI_VERSION_1_4)) {
        LOGI(" on jni load get current env failed ");
        return result;
    }

    /**
     * 映射java的类到C++当中
     * */
    tmpClazz = currentEnv->FindClass(CLAZZ_EVENTINFO);
    if(tmpClazz == NULL){
        LOGI(" can not find class  %s" ,CLAZZ_EVENTINFO);
        return result;
    }
    gclazzEventInfo= (jclass)currentEnv->NewGlobalRef(tmpClazz);
    /**
     * 这里特别要强调的是，Android4.0之后，垃圾回收机制修改了，如果使用如下方法
     * 获取全局的类引用，在查找类方法时，会导致崩溃
     * tmp = currentEnv->FindClass(CLAZZ_EVENTINFO);
     * jmethodID eventConstructMethod = currentEnv->GetMethodID(gclazzEventInfo,"<init>","()V");
     *
     * 必须使用使用 NewGlobalRef方法将该引用从局部引用改成全局引用，否则函数退出，该引用失效
     * */

    tmpClazz = currentEnv->FindClass(CLAZZ_JNIPROCESSOR);
    if(tmpClazz == NULL){
        LOGI(" can not find class  %s" ,CLAZZ_JNIPROCESSOR);
        return result;
    }
    gclazzJNIProcessor = (jclass)currentEnv->NewGlobalRef(tmpClazz);

    tmpClazz = currentEnv->FindClass(CLAZZ_MAINACTIVITY);
    if(tmpClazz == NULL){
        LOGI(" can not find class  %s" ,CLAZZ_MAINACTIVITY);
        return result;
    }
    gclazzMainActivity = (jclass)currentEnv->NewGlobalRef(tmpClazz);

    tmpClazz = currentEnv->FindClass(CLAZZ_EVENTTYPE);
    if(tmpClazz == NULL){
        LOGI(" can not find class  %s" ,CLAZZ_EVENTTYPE);
        return result;
    }
    gclazzEventType = (jclass)currentEnv->NewGlobalRef(tmpClazz);

    /**
     * 第三个参数是这个函数的签名，不知道签名是什么的话可以去class文件目录下面用javap命令查看
     * 例如,EventInfo的类的函数签名可以用命令javap -s EventInfo查看
     * 因为我们要查找的是EventInfo的构造函数，所以第二个参数应该填 <init>而不是EventInfo
     *
     * 使用 GetMethodID(gclazzEventInfo,"EventInfo","(ILjava/lang/String;)V");会导致程序Crash
     * */
    tmpMethodID = currentEnv->GetMethodID(gclazzEventInfo,"<init>","(ILjava/lang/String;Lexample/bill/com/jnilearn/EventInfo$EventType;)V");
    if(tmpMethodID == NULL){
        LOGI(" can not find method id EventInfo 111");
        return result;
    }
    gNewEventMethod = tmpMethodID;

    tmpMethodID = currentEnv->GetStaticMethodID(gclazzJNIProcessor,"nativeCallbackFunc","(Lexample/bill/com/jnilearn/EventInfo;)V");
    if(tmpMethodID == NULL){
        LOGI(" can not find method id nativeCallbackFunc");
        return result;
    }
    gProcessNativeMethod = tmpMethodID;

    return JNI_VERSION_1_4;
}

extern "C"
JNIEXPORT void JNICALL Java_example_bill_com_jnilearn_MainActivity_testJNIEnv(
        JNIEnv *env,
        jobject /* this */) {

    /**
     * 不同的线程调用该函数，得到的JNIEnv的变量的地址是不一样的
     * JNIEnv是为每个jvm的线程保存的变量，不可以跨线程使用
     * 尽量不要保存JNIEnv变量，直接通过 AttachCurrentThread 获取
     *
     * 由于该方法是参照JNI规范所写的native函数，所以参数传入的JNIEnv和通过AttachCurrentThread获取到的JNIEnv地址一样
     * 如果不按照JNI规范些的函数，则需要通过AttachCurrentThread来获取JNIEnv变量
     *
     * */
    LOGI(" argument env address is %p ",env);

    JNIEnv *currentEnv;
    int status = gJvm->AttachCurrentThread(&currentEnv,NULL);

    LOGI(" current thread env address is %p ",env);

    /**
     * 如果是java层调用的该函数，则不能调用 DetachCurrentThread，否则会引起崩溃
     * */
    //gJvm->DetachCurrentThread();
    return;
}

jobject getEventType(JNIEnv *env,EventType eventType){
    jfieldID id = NULL;
    LOGI(" get event type %d ",eventType);
    switch(eventType){
        case en_event_ok:
            id = env->GetStaticFieldID(gclazzEventType, "en_event_ok", "Lexample/bill/com/jnilearn/EventInfo$EventType;");
            break;
        case en_event_failed:
            id = env->GetStaticFieldID(gclazzEventType, "en_event_failed", "Lexample/bill/com/jnilearn/EventInfo$EventType;");
            break;
        default:
            return NULL;
    }

    return env->GetStaticObjectField(gclazzEventType,id);
}

void event_callback_func(EventInfo *eventInfo){

    bool isAttacked = false;
    JNIEnv *currentEnv;

    int status =gJvm->GetEnv((void **) &currentEnv, JNI_VERSION_1_4);
    if(status < 0){
        status = gJvm->AttachCurrentThread(&currentEnv, NULL);

        if(status < 0) {
            LOGI("event_callback_func: failed to attach current thread");
            return;
        }
        isAttacked = true;
    }

    if(NULL == gclazzEventInfo || NULL == gclazzJNIProcessor || NULL == gclazzEventType){
        LOGI(" class %s , %s , %s  has not mapped yet",CLAZZ_EVENTINFO,CLAZZ_JNIPROCESSOR,CLAZZ_EVENTTYPE);
        gJvm->DetachCurrentThread();
        return;
    }

    if(NULL == gNewEventMethod){
        LOGI(" xdag event construct method not found ");
        return;
    }

    /**
     * 找到EventInfo的构造方法，并实例化对象
     * */
    if(NULL == gNewEventMethod){
        LOGI(" can not find construct function of EventInfo ");
        gJvm->DetachCurrentThread();
        return;
    }

    /**
     * 将C++当中的类类型转成java当中引用的类型,int类型是基本类型，不用转换
     *
     * */
    LOGI("start get event type ");
    jobject eventType = getEventType(currentEnv,eventInfo->eventType);
    if(NULL == eventType){
        LOGI("can not get eventType ");
        return;
    }
    jint jmsgNo = eventInfo->msgNo;
    jstring jmsgData = currentEnv->NewStringUTF(eventInfo->msgData.c_str());


    LOGI("construct event info object ");
    jobject jeventInfo = currentEnv->NewObject(gclazzEventInfo,gNewEventMethod,jmsgNo,jmsgData,eventType);
    if(NULL == jeventInfo){
        LOGI(" can construct object of EventInfo ");
        gJvm->DetachCurrentThread();
        return;
    }

    /**
     * 回调java当中的方法，把对象当做参数进行传递
     * */
    if(NULL == gProcessNativeMethod){
        LOGI("can not find callback function of JNIProcessor");
        gJvm->DetachCurrentThread();
        return;
    }

    currentEnv->CallStaticVoidMethod(gclazzJNIProcessor,gProcessNativeMethod,jeventInfo);

//error:
    /**
     * 回调完成后一定要释放JNI环境
     * */
    if(isAttacked){
        gJvm->DetachCurrentThread();
    }
}

void* work_thread(void *arg){

    int msgNo = 0;

    LOGI(" work thread start");

    while(msgNo < 100){
        EventInfo info;
        info.msgNo = msgNo;
        info.msgData = "msg data from native";
        info.eventType = en_event_failed;
        LOGI(" send msg data to ui msgno  %d ",msgNo);
        event_callback_func(&info);
        msgNo ++;
        sleep(1);
    }
}

extern "C"
JNIEXPORT void JNICALL Java_example_bill_com_jnilearn_MainActivity_startNativeThread(
        JNIEnv *env,
        jobject /* this */){
    /*
     * 开启pthread
     * */
    pthread_create(&tid_work_thread,NULL,work_thread,NULL);
    pthread_detach(tid_work_thread);

}



